/*
 * $Id: vpn.c,v 1.6 2006-04-03 10:25:08 bacon Exp $
 */

#include <xp/msw/vpn.h>
#include <xp/bas/memory.h>
#include <xp/bas/string.h>
#include <xp/bas/time.h>
#include <xp/bas/assert.h>

#include <windows.h>
#include <ras.h>
#include <raserror.h>

xp_vpn_t* xp_vpn_open (xp_vpn_t* vpn, const xp_char_t* name)
{
	if (vpn == XP_NULL) 
	{
		vpn = (xp_vpn_t*)xp_malloc(xp_sizeof(xp_vpn_t));
		if (vpn == XP_NULL) return XP_NULL;
		vpn->__dynamic = xp_true;
	}
	else vpn->__dynamic = xp_false;

	vpn->name = xp_strdup(name);
	if (vpn->name == XP_NULL) 
	{
		if (vpn->__dynamic) xp_free (vpn);
		return XP_NULL;
	}

	vpn->handle = XP_NULL;
	return vpn;
}

int xp_vpn_close (xp_vpn_t* vpn)
{
	if (xp_vpn_disconnect(vpn) == -1) return -1;
	if (vpn->name != XP_NULL) xp_free (vpn->name);
	if (vpn->__dynamic) xp_free (vpn);
	return 0;
}

static void CALLBACK __dial_callback (
	HRASCONN conn, UINT msg, RASCONNSTATE state, DWORD err, DWORD eerr)
{
	/* don't do anything for the time being */
}

int xp_vpn_connect (xp_vpn_t* vpn, 
	const xp_char_t* server, const xp_char_t* username,
	const xp_char_t* password, const xp_char_t* domain)
{
	HRASCONN handle = XP_NULL;
	RASDIALPARAMS dial_params;
	DWORD n;

	/* already connected */
	if (vpn->handle != XP_NULL) return -1;

	xp_memset (&dial_params, 0, xp_sizeof(dial_params));
	dial_params.dwSize = xp_sizeof(dial_params);

	xp_strxcpy (dial_params.szEntryName, 
		xp_countof(dial_params.szEntryName), vpn->name);
	xp_strxcpy (dial_params.szPhoneNumber,
		xp_countof(dial_params.szPhoneNumber), server);
	xp_strxcpy (dial_params.szUserName, 
		xp_countof(dial_params.szUserName), username);
	xp_strxcpy (dial_params.szPassword, 
		xp_countof(dial_params.szPassword), password);
	xp_strxcpy (dial_params.szDomain,
		xp_countof(dial_params.szDomain), domain);

	dial_params.szCallbackNumber[0] = XP_CHAR('\0');

	n = RasDial (
		XP_NULL,         /* lpRasDialExtensions */
		XP_NULL,         /* lpszPhonebook */
		&dial_params,    /* lpRasDialParams */
		1,               /* dwNotifierType */
		__dial_callback, /* lpvNotifier */
		&handle          /* lphRasConn */
	);
	if (n != 0) return -1;

	xp_assert (xp_sizeof(vpn->handle) >= xp_sizeof(handle));
	vpn->handle = (void*)handle;
	xp_sleep (500);
	return 0;
}

int xp_vpn_disconnect (xp_vpn_t* vpn)
{
	if (vpn->handle != XP_NULL) 
	{
		if (RasHangUp(vpn->handle) != 0) return -1;
		while (1) 
		{
			if (xp_vpn_getstatus(vpn,XP_NULL) == -1) break;
			Sleep (0);
		}
		vpn->handle = XP_NULL;
	}

	return 0;
}

int xp_vpn_getstatus (xp_vpn_t* vpn, int* status)
{
	DWORD n;
	RASCONNSTATUS tmp;

	if (vpn->handle == XP_NULL) return -1;

	tmp.dwSize = xp_sizeof(tmp);
	n = RasGetConnectStatus(vpn->handle, &tmp);
	if (n != 0) return -1;

	if (status != XP_NULL)
	{
		if (tmp.rasconnstate == RASCS_Connected) 
		{
			*status = XP_VPN_CONNECTED;
		}
		else if (tmp.rasconnstate == RASCS_Disconnected)
		{
			*status = XP_VPN_DISCONNECTED;
		}
		else if (tmp.rasconnstate >= RASCS_Authenticate &&
		         tmp.rasconnstate <= RASCS_ReAuthenticate)
		{
			*status = XP_VPN_AUTHENTICATING;
		}
		else if (tmp.rasconnstate == RASCS_Authenticated)
		{
			*status = XP_VPN_AUTHENTICATED;
		}
		else *status = XP_VPN_CONNECTING;
	}

	return 0;		
}

int xp_createvpnentry (const xp_char_t* name, const xp_char_t* server, int opt)
{
	RASENTRY entry;
	RASDEVINFO* devs;
	DWORD size, ndevs, n, i;

	xp_memset (&entry, 0, xp_sizeof(entry));
	entry.dwSize = xp_sizeof(entry);
	xp_strxcpy (entry.szDeviceType, 
		xp_countof(entry.szDeviceType), RASDT_Vpn);

	xp_strxcpy (entry.szLocalPhoneNumber,
		xp_countof(entry.szLocalPhoneNumber), 
		((server == XP_NULL)? XP_TEXT(""): server));

	size = xp_sizeof(RASDEVINFO);
	while (1) 
	{
		devs = (RASDEVINFO*)xp_malloc(size);
		if (devs == XP_NULL) break;

		devs->dwSize = xp_sizeof(RASDEVINFO);
		n = RasEnumDevices (devs, &size, &ndevs);
		if (n == 0) break;
		if (n != ERROR_BUFFER_TOO_SMALL) break;

		xp_free (devs);
	}

	if (devs == XP_NULL) return -1;

	for (i = 0; i < ndevs; i++) 
	{
		if (xp_strcmp(devs[i].szDeviceType, RASDT_Vpn) == 0) 
		{
			if (xp_strstr(devs[i].szDeviceName,XP_TEXT("PPTP")) != XP_NULL) 
			{
				xp_strxcpy (entry.szDeviceName, 
					xp_countof(entry.szDeviceName),
					devs[i].szDeviceName);
				break;
			}
			else 
			{
				xp_strxcpy (entry.szDeviceName, 
					xp_countof(entry.szDeviceName),
					devs[i].szDeviceName);
			}
		}
	}

	xp_free (devs);

	if (entry.szDeviceName[0] == XP_CHAR('\0')) return -1;
	entry.dwfOptions |= RASEO_SwCompression;
	entry.dwfOptions |= RASEO_IpHeaderCompression;

	if (opt & XP_VPN_NETWORK_LOGON) 
		entry.dwfOptions |= RASEO_NetworkLogon;
	if (opt & XP_VPN_USE_LOGON_CREDENTIALS)
		entry.dwfOptions |= RASEO_UseLogonCredentials;
#ifdef RASEO_SecureLocalFiles
	if (opt & XP_VPN_SECURE_LOCAL_FILES)
		entry.dwfOptions |= RASEO_SecureLocalFiles;
#endif
#ifdef RASEO_ShowDialingProgress
	if (opt & XP_VPN_SHOW_PROGRESS)
		entry.dwfOptions |= RASEO_ShowDialingProgress;
#endif
	if (opt & XP_VPN_REMOTE_DEFAULT_GATEWAY)
		entry.dwfOptions |= RASEO_RemoteDefaultGateway;
	if (opt & XP_VPN_REQUIRE_DATA_ENCRYPTION)
		entry.dwfOptions |= RASEO_RequireDataEncryption;
	if (opt & XP_VPN_REQUIRE_ENCRYPTED_PASSWORD)
		entry.dwfOptions |= RASEO_RequireEncryptedPw;

	// TODO:
	//entry.dwfStrategy = VS_PptpOnly;

	// create an entry or modifies it if it exists
	n = RasSetEntryProperties (
		XP_NULL, name, &entry, xp_sizeof(entry), XP_NULL, 0);
	if (n != 0) return -1;

	return 0;
}

int xp_removevpnentry (const xp_char_t* name)
{
	return (RasDeleteEntry(XP_NULL,name) == 0)? 0: -1;
}
